package com.newsnetwork.article.service.impl;

import com.github.pagehelper.PageHelper;
import com.mongodb.client.gridfs.GridFSBucket;
import com.newsnetwork.api.config.RabbitMQConfig;
import com.newsnetwork.api.config.RabbitMQDelayConfig;
import com.newsnetwork.api.service.BaseService;
import com.newsnetwork.article.mapper.ArticleMapper;
import com.newsnetwork.article.mapper.ArticleMapperCustom;
import com.newsnetwork.article.service.ArticleService;
import com.newsnetwork.enums.ArticleAppointType;
import com.newsnetwork.enums.ArticleReviewLevel;
import com.newsnetwork.enums.ArticleReviewStatus;
import com.newsnetwork.enums.YesOrNo;
import com.newsnetwork.exception.GraceException;
import com.newsnetwork.grace.result.CommonJSONResult;
import com.newsnetwork.grace.result.ResponseStatusEnum;
import com.newsnetwork.pojo.Article;
import com.newsnetwork.pojo.ArticlePublicationStatus;
import com.newsnetwork.pojo.Category;
import com.newsnetwork.pojo.bo.NewArticleBO;
import com.newsnetwork.pojo.vo.ArticleDetailVO;
import com.newsnetwork.utils.JsonUtils;
import com.newsnetwork.utils.PagedGridResult;
import com.newsnetwork.utils.extend.AliTextReviewUtils;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.bson.types.ObjectId;
import org.n3r.idworker.Sid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.client.RestTemplate;
import tk.mybatis.mapper.entity.Example;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author DingNing
 * @date 2022/11/11 9:57
 * @Description:
 */
@Service
public class ArticleServiceImpl extends BaseService implements ArticleService {

    private static final Logger log = LoggerFactory.getLogger(ArticleServiceImpl.class);

    @Autowired
    private ArticleMapper articleMapper;
    @Autowired
    private ArticleMapperCustom articleMapperCustom;
    @Autowired
    private Sid sid;
    @Autowired
    private AliTextReviewUtils aliTextReviewUtils;
    @Autowired
    private RestTemplate restTemplate;

    /**
     * 发布文章
     * @param newArticleBO
     * @param category
     * @return
     */
    @Transactional
    @Override
    public ArticlePublicationStatus createArticle(NewArticleBO newArticleBO, Category category) {

        String articleId = sid.nextShort();

        Article article = new Article();
        BeanUtils.copyProperties(newArticleBO, article);

        article.setId(articleId);
        article.setCategoryId(category.getId());
        article.setArticleStatus(ArticleReviewStatus.REVIEWING.type);
        article.setCommentCounts(0);
        article.setReadCounts(0);

        article.setIsDelete(YesOrNo.NO.type);
        article.setCreateTime(new Date());
        article.setUpdateTime(new Date());

        if (article.getIsAppoint() == ArticleAppointType.TIMING.type) {
            article.setPublishTime(newArticleBO.getPublishTime());
        } else if (article.getIsAppoint() == ArticleAppointType.IMMEDIATELY.type) {
            article.setPublishTime(new Date());
        }



        int res = articleMapper.insert(article);
        if (res != 1) {
            GraceException.display(ResponseStatusEnum.ARTICLE_CREATE_ERROR);
        }

        // 发送延迟消息到mq，计算定时发布时间和当前时间的时间差，则为往后延迟的时间
        if (article.getIsAppoint() == ArticleAppointType.TIMING.type) {

            Date endDate = newArticleBO.getPublishTime();
            Date startDate = new Date();
            int delayTimes = (int)(endDate.getTime() - startDate.getTime());

            MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
                @Override
                public Message postProcessMessage(Message message) throws AmqpException {
                    // 设置消息的持久
                    message.getMessageProperties()
                            .setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                    // 设置消息延迟的时间，单位ms毫秒
                    message.getMessageProperties()
                            .setDelay(delayTimes);
                    return message;
                }
            };
            rabbitTemplate.convertAndSend(
                    RabbitMQDelayConfig.EXCHANGE_DELAY,
                    "publish.delay.display",
                    articleId,
                    messagePostProcessor);
        }

        /**
         * FIXME: 我们只检测正常的词汇，非正常词汇大家课后去检测
         */
        // 通过阿里智能AI实现对文章文本的自动检测（自动审核）
        String reviewTextResult = aliTextReviewUtils.reviewTextContent(newArticleBO.getContent());
        // 全部设置为需要人工审核
//        String reviewTextResult = ArticleReviewLevel.REVIEW.type;

        if (reviewTextResult
                .equalsIgnoreCase(ArticleReviewLevel.PASS.type)) {
            // 修改当前的文章，状态标记为审核通过
            this.updateArticleStatus(articleId, ArticleReviewStatus.SUCCESS.type);
        } else if (reviewTextResult
                .equalsIgnoreCase(ArticleReviewLevel.REVIEW.type)) {
            // 修改当前的文章，状态标记为需要人工审核
            this.updateArticleStatus(articleId, ArticleReviewStatus.WAITING_MANUAL.type);
        } else if (reviewTextResult
                .equalsIgnoreCase(ArticleReviewLevel.BLOCK.type)) {
            // 修改当前的文章，状态标记为审核未通过
            this.updateArticleStatus(articleId, ArticleReviewStatus.FAILED.type);
        }
        ArticlePublicationStatus articlePublicationStatus = new ArticlePublicationStatus();
        articlePublicationStatus.setArticleId(articleId);
        articlePublicationStatus.setArticleStatus(reviewTextResult);
        return articlePublicationStatus;
    }

    /**
     * 更新定时发布为即时发布
     */
    @Transactional
    @Override
    public void updateAppointToPublish() {
        articleMapperCustom.updateAppointToPublish();
    }

    /**
     * 更新单条文章为即时发布
     *
     * @param articleId
     */
    @Transactional
    @Override
    public void updateArticleToPublish(String articleId) {
        Article article = new Article();
        article.setId(articleId);
        article.setIsAppoint(ArticleAppointType.IMMEDIATELY.type);
        articleMapper.updateByPrimaryKeySelective(article);
    }

    /**
     * 查询文章发布状态
     * @param articleId
     */
    @Override
    public int queryArticleAppoint(String articleId) {
        return articleMapperCustom.queryArticleAppoint(articleId);
    }


    /**
     * 关联文章和 GridFS 的html文件id
     *
     * @param articleId      文章id
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateArticleToGridFS(String articleId) throws TemplateException, IOException {
        String articleMongoId = createArticleHTMLToGridFS(articleId);
        log.warn("updateArticleToGridFS : articleId : {}", articleId);
        log.warn("updateArticleToGridFS : articleMongoId : {}", articleMongoId);
        Article article = new Article();
        article.setId(articleId);
        article.setMongoFileId(articleMongoId);

        int res = articleMapper.updateByPrimaryKeySelective(article);
        if (res != 1) {
            GraceException.display(ResponseStatusEnum.ARTICLE_REVIEW_ERROR);
        }

//        doDownloadArticleHTML(articleId, articleMongoId);
        doDownloadArticleHTMLByMQ(articleId, articleMongoId);
    }

    // 通过MQ改进，此方法以弃用
    @Deprecated
    private void doDownloadArticleHTML(String articleId, String articleMongoId) {
        String url = "http://html.newsnetwork.com:8002/article/html/download?articleId="
                + articleId + "&articleMongoId=" + articleMongoId;
        ResponseEntity<Integer> forEntity = restTemplate.getForEntity(url, Integer.class);
        Integer status = forEntity.getBody();
        if (!status.equals(HttpStatus.OK.value())) {
            GraceException.display(ResponseStatusEnum.ARTICLE_REVIEW_ERROR);
        }
    }

    @Autowired
    private RabbitTemplate rabbitTemplate;

    private void doDownloadArticleHTMLByMQ(String articleId, String articleMongoId) {

        rabbitTemplate.convertAndSend(
                RabbitMQConfig.EXCHANGE_ARTICLE,
                "article.download.do",
                articleId + "," + articleMongoId);
    }

    @Autowired
    private GridFSBucket gridFSBucket;

    /**
     * 文章页通过 FreeMarker 模板生成静态页面, 并上传到 GridFS
     * @param articleId 文章id
     */
    public String createArticleHTMLToGridFS(String articleId) throws IOException, TemplateException {
        // 配置freemarker基本环境
        Configuration cfg = new Configuration(Configuration.getVersion());
        // 声明freemarker模板所需要加载的目录的位置
        String classpath = this.getClass().getResource("/").getPath();
        cfg.setDirectoryForTemplateLoading(new File(classpath + "templates"));

        //获得模板ftl文件
        Template template = cfg.getTemplate("detail.ftl", "utf-8");

        //发起远程调用ArticlePortalController. 获得文章的详情数据
        ArticleDetailVO articleDetail = getArticleDetail(articleId);
        Map<String, Object> map = new HashMap<>();
        //articleDetail 是模板写好的需要的对象的名称
        map.put("articleDetail", articleDetail);

        String htmlContent = FreeMarkerTemplateUtils.processTemplateIntoString(template, map);
        InputStream inputStream = IOUtils.toInputStream(htmlContent);
        ObjectId fileId = gridFSBucket.uploadFromStream(articleDetail.getId() + ".html", inputStream);
        log.error("fileId = {}", fileId);
        return fileId.toString();
    }

    /**
     * 注入服务发现, 可以获得已经注册的服务相关信息
     */
//    @Autowired
//    private DiscoveryClient discoveryClient;

    private ArticleDetailVO getArticleDetail(String articleId) {
        String serviceId = "SERVICE-ARTICLE";
//        List<ServiceInstance> instanceList = discoveryClient.getInstances(serviceId);
//        ServiceInstance articleService = instanceList.get(0);

        String url = "http://" + serviceId + "/portal/article/detail?articleId=" + articleId;
        ResponseEntity<CommonJSONResult> responseEntity = restTemplate.getForEntity(url, CommonJSONResult.class);
        CommonJSONResult bodyResult = responseEntity.getBody();
        ArticleDetailVO articleDetailVO = null;
        if (bodyResult.getStatus() == 200) {
            String userJson = JsonUtils.objectToJson(bodyResult.getData());
            articleDetailVO = JsonUtils.jsonToPojo(userJson, ArticleDetailVO.class);
        }
        return articleDetailVO;
    }

    /**
     * 用户中心 - 查询我的文章列表
     *
     * @param userId
     * @param keyword
     * @param status
     * @param startDate
     * @param endDate
     * @param page
     * @param pageSize
     */
    @Override
    public PagedGridResult queryMyArticleList(String userId,
                                              String keyword,
                                              Integer status,
                                              Date startDate,
                                              Date endDate,
                                              Integer page,
                                              Integer pageSize) {
        Example example = new Example(Article.class);
        example.orderBy("createTime").desc();
        Example.Criteria criteria = example.createCriteria();

        criteria.andEqualTo("publishUserId", userId);

        if (StringUtils.isNotBlank(keyword)) {
            criteria.andLike("title", "%" + keyword + "%");
        }

        if (ArticleReviewStatus.isArticleStatusValid(status)) {
            criteria.andEqualTo("articleStatus", status);
        }

        if (status != null && status == 12) {
            criteria.andEqualTo("articleStatus", ArticleReviewStatus.REVIEWING.type)
                    .orEqualTo("articleStatus", ArticleReviewStatus.WAITING_MANUAL.type);
        }

        criteria.andEqualTo("isDelete", YesOrNo.NO.type);

        if (startDate != null) {
            criteria.andGreaterThanOrEqualTo("publishTime", startDate);
        }
        if (endDate != null) {

            criteria.andLessThanOrEqualTo("publishTime", endDate);
        }

        PageHelper.startPage(page, pageSize);
        List<Article> list = articleMapper.selectByExample(example);
        return setterPagedGrid(list, page);
    }

    /**
     * 更改文章的状态
     * @param articleId
     * @param pendingStatus
     */
    @Transactional
    @Override
    public void updateArticleStatus(String articleId, Integer pendingStatus) {
        Example example = new Example(Article.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("id", articleId);

        Article pendingArticle = new Article();
        pendingArticle.setArticleStatus(pendingStatus);

        int res = articleMapper.updateByExampleSelective(pendingArticle, example);
        if (res != 1) {
            GraceException.display(ResponseStatusEnum.ARTICLE_REVIEW_ERROR);
        }
    }

    /**
     * 管理员查询文章列表
     * @param status
     * @param page
     * @param pageSize
     */
    @Override
    public PagedGridResult queryAllArticleListAdmin(Integer status, Integer page, Integer pageSize) {
        Example articleExample = new Example(Article.class);
        articleExample.orderBy("createTime").desc();

        Example.Criteria criteria = articleExample.createCriteria();
        if (ArticleReviewStatus.isArticleStatusValid(status)) {
            criteria.andEqualTo("articleStatus", status);
        }

        // 审核中是机审和人审核的两个状态，所以需要单独判断
        if (status != null && status == 12) {
            criteria.andEqualTo("articleStatus", ArticleReviewStatus.REVIEWING.type)
                    .orEqualTo("articleStatus", ArticleReviewStatus.WAITING_MANUAL.type);
        }

        //isDelete 必须是0
        criteria.andEqualTo("isDelete", YesOrNo.NO.type);

        /**
         * page: 第几页
         * pageSize: 每页显示条数
         */
        PageHelper.startPage(page, pageSize);
        List<Article> list = articleMapper.selectByExample(articleExample);
        return setterPagedGrid(list, page);
    }

    /**
     * 删除文章
     * @param userId
     * @param articleId
     */
    @Transactional
    @Override
    public void deleteArticle(String userId, String articleId) {
        Example articleExample = makeExampleCriteria(userId, articleId);

        Article pending = new Article();
        pending.setIsDelete(YesOrNo.YES.type);

        int result = articleMapper.updateByExampleSelective(pending, articleExample);
        if (result != 1) {
            GraceException.display(ResponseStatusEnum.ARTICLE_DELETE_ERROR);
        }
        doDeleteArticleHTML(articleId);
    }

    /**
     * 撤回文章
     * @param userId
     * @param articleId
     */
    @Transactional
    @Override
    public void withdrawArticle(String userId, String articleId) {
        Example articleExample = makeExampleCriteria(userId, articleId);

        Article pending = new Article();
        pending.setArticleStatus(ArticleReviewStatus.WITHDRAW.type);

        int result = articleMapper.updateByExampleSelective(pending, articleExample);
        if (result != 1) {
            GraceException.display(ResponseStatusEnum.ARTICLE_WITHDRAW_ERROR);
        }
        doDeleteArticleHTML(articleId);
    }

    private void doDeleteArticleHTML(String articleId) {
        //查询文章 mongo_file_id
        Article article = articleMapper.selectByPrimaryKey(articleId);
        String mongoFileId = article.getMongoFileId();
        //删除 GridFS 上的文件
        gridFSBucket.delete(new ObjectId(mongoFileId));
        //删除前端服务器上的静态页面
        doDeleteArticleHTMLByMQ(articleId);
    }

    private void doDeleteArticleHTMLByMQ(String articleId) {
        rabbitTemplate.convertAndSend(
                RabbitMQConfig.EXCHANGE_ARTICLE,
                "article.html.download.do",
                articleId);
    }

    private Example makeExampleCriteria(String userId, String articleId) {
        Example articleExample = new Example(Article.class);
        Example.Criteria criteria = articleExample.createCriteria();
        criteria.andEqualTo("publishUserId", userId);
        criteria.andEqualTo("id", articleId);
        return articleExample;
    }

    /**
     * 优化成通过MQ，此方法以弃用
     * 远程调用前端服务器的微服务删除
     * @param articleId 文章id
     */
    @Deprecated
    private void doDeleteArticleHTMLFromFontEnd(String articleId) {
        String url = "http://html.newsnetwork.com:8002/article/html/delete?articleId=" + articleId;
        ResponseEntity<Integer> forEntity = restTemplate.getForEntity(url, Integer.class);
        Integer status = forEntity.getBody();
        if (!status.equals(HttpStatus.OK.value())) {
            GraceException.display(ResponseStatusEnum.ARTICLE_REVIEW_ERROR);
        }
    }
}
